#pragma once
#include "Mapper.h"

/// <summary>
/// 
/// </summary>
class Mapper004 :public Mapper
{
private:
	uint8_t mRegid = 0;	// �Ĵ������
	uint8_t mPrmode = 0;	// RPG-Rom �л�ģʽ
	uint8_t mCrmode = 0;	// CHR-Rom �л�ģʽ
	uint8_t mRegs[8] = { 0,2,4,5,6,7,0,1 };  //�Ĵ����б�
	uint8_t latch = 0xFF;	// ����ֵ
	uint8_t counter = 0;	// ������
	uint8_t mIrqEnable = 0;	// IRQʹ��
	uint8_t mIrqReload = 0;	// IRQʹ��

public:

	Mapper004(Famicom* fc, famicon_rom_t* rom) :Mapper(fc, rom) {
	

	}

	virtual void OnReset() override {

		mRegid = 0;
		mPrmode = 0;
		mCrmode = 0;
		latch = 0;
		counter = 0;
		mIrqEnable = 0;


		//FormatBank(BANKSIZE_8KB, BANKSIZE_1KB);
		SetCPUBank_8KB(0, 0);
		SetCPUBank_8KB(1, 1);
		const int last = mROM->prg16kbCount * 2;
		SetCPUBank_8KB(2, last - 2);
		SetCPUBank_8KB(3, last - 1);

		// CHR-Rom
		for (int i = 0; i != 8; ++i)
			SetPPUBank_1KB(i, i);
	}

	virtual void Hsync(uint32_t line) override {
		if (!(famicom->fcPPU->mPPUMask
			& (((uint8_t)BGEN | (uint8_t)SPEN))))
			return;

		// �����ɼ�ɨ����
		if (line > 239)
			return;

		if (mIrqReload) {
			counter = latch;
			mIrqReload = 0;
		}
		else if (counter > 0)
			counter--;

		if (counter == 0) {
			mIrqReload = 0xFF;
			if (mIrqEnable) {
				famicom->fcCPU->TryIrq(IRQ_HSYNC_INT);
			}
		}
#if 0
		if (counter) {
			--counter;
			if (!counter && mIrqEnable) {
				famicom->cpu->TryIrq(IRQ_HSYNC_INT);
			}
		}
		else {
			counter = latch;
		}
#endif
	}
	void SelectBank(uint8_t data)
	{
		mRegid = data & 7;
		mPrmode = (data >> 6) & 1;
		mCrmode = (data >> 7) & 1;
		UpdatePPUBanks();
		UpdateCPUBanks();

	}

	void UpdateBankData(uint8_t bank)
	{
		if (mRegid <= 5) {
			mRegs[mRegid] = bank;
			UpdatePPUBanks();
		}
		else {
			mRegs[mRegid] = bank & 0x3F;
			UpdateCPUBanks();
		}
	}

	void UpdateCPUBanks() {
		const int last = mROM->prg16kbCount * 2;
		if (mPrmode) {
			// (-2) R7 R6 (-1)
			SetCPUBank_8KB(0, last - 2);
			SetCPUBank_8KB(1, mRegs[7]);
			SetCPUBank_8KB(2, mRegs[6]);
			SetCPUBank_8KB(3, last - 1);
		}
		else {
			// R6 R7 (-2) (-1)
			SetCPUBank_8KB(0, mRegs[6]);
			SetCPUBank_8KB(1, mRegs[7]);
			SetCPUBank_8KB(2, last - 2);
			SetCPUBank_8KB(3, last - 1);
		}
	}
	void UpdatePPUBanks() {
		if (mCrmode) {
			// R2R3R4R5R0r0R1r1
			SetPPUBank_1KB(0, mRegs[2]);
			SetPPUBank_1KB(1, mRegs[3]);
			SetPPUBank_1KB(2, mRegs[4]);
			SetPPUBank_1KB(3, mRegs[5]);
			SetPPUBank_1KB(4, mRegs[0] & 0xFE);
			SetPPUBank_1KB(5, mRegs[0] | 1);
			SetPPUBank_1KB(6, mRegs[1] & 0xFE);
			SetPPUBank_1KB(7, mRegs[1] | 1);
		}
		else {
			// R0r0R1r1R2R3R4R5
			SetPPUBank_1KB(0, mRegs[0] & 0xFE);
			SetPPUBank_1KB(1, mRegs[0] | 1);
			SetPPUBank_1KB(2, mRegs[1] & 0xFE);
			SetPPUBank_1KB(3, mRegs[1] | 1);
			SetPPUBank_1KB(4, mRegs[2]);
			SetPPUBank_1KB(5, mRegs[3]);
			SetPPUBank_1KB(6, mRegs[4]);
			SetPPUBank_1KB(7, mRegs[5]);
		}


	}

	void UpdateMirroring(uint8_t data)
	{
		if (!mROM->isFourScreen) {
			famicom->fcPPU->SwitchMirroring((data & 1)
				? Mirror::HORIZONTAL
				: Mirror::VERTICAL);
		}

	}



	virtual void WriteViaCPU(uint16_t address, uint8_t data) override {
		switch (address & 0xE001)
		{
		case 0x8000:
			SelectBank(data);
			break;
		case 0x8001:
			UpdateBankData(data);
			break;
		case 0xA000:
			// $A000-$BFFE Mirroring
			UpdateMirroring(data);
			break;
		case 0xA001:
			// $A001-$BFFF PRG RAM protect
			if (data & 0x80)
			{
				// enable save RAM $6000-$7FFF
			}
			else
			{
				// disable save RAM $6000-$7FFF
			}
			break;
		case 0xC000:
			// $C000-$DFFE IRQ latch
			latch = data;
			break;
		case 0xC001:
			// $C001-$DFFF IRQ reload
			mIrqReload = 0xFF;
			counter = 0;
			break;
		case 0xE000:
			// $E000-$FFFE IRQ disable
			mIrqEnable = 0;
			famicom->fcCPU->ClrIrq(IRQ_HSYNC_INT);
			break;
		case 0xE001:
			// $E001-$FFFF IRQ enable
			mIrqEnable = 1;
			break;
		}
	}

	virtual void OnSave(SaveBundle* bundle) override
	{
		bundle->SaveByte(mRegid);
		bundle->SaveByte(mPrmode);
		bundle->SaveByte(mCrmode);
		bundle->SaveBlock(mRegs, 8);
		bundle->SaveByte(latch);
		bundle->SaveByte(counter);
		bundle->SaveByte(mIrqEnable);
		bundle->SaveByte(mIrqReload);
	}

	virtual void OnRestore(SaveBundle* bundle) override
	{
		mRegid = bundle->RestoreByte();
		mPrmode = bundle->RestoreByte();
		mCrmode = bundle->RestoreByte();
		bundle->RestoreBlock(mRegs, 8);
		latch = bundle->RestoreByte();
		counter = bundle->RestoreByte();
		mIrqEnable = bundle->RestoreByte();
		mIrqReload = bundle->RestoreByte();
	}

};